using System.Collections.Generic;
using Lucene.Linq.Search;
using System;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;

namespace Lucene.Linq.Storage
{
    /// <summary>
    /// Entity Manager, read and write entity from storage system
    /// </summary>
    public class StorageManager : IDisposable
    {
        protected Dictionary<Type, IEntityStorage> StorageMedium;
        protected IndexSet IndexSet;

        #region Constructors

        /// <summary>
        /// Default constructor
        /// </summary>
        public StorageManager(Directory directory)
        {
            StorageMedium = new Dictionary<Type, IEntityStorage>();
            IndexSet = new IndexSet(directory);
        }

        #endregion

        /// <summary>
        /// Adds a new entity to storage
        /// </summary>
        /// <param name="entity">The entity to store</param>
        public void Add(IEntity entity)
        {
            Type genericEntityType = entity.GetType();

            if (StorageMedium.ContainsKey(genericEntityType))
            {
                GetIndexSet(entity).Add(entity);
                StorageMedium[genericEntityType].Put(entity);
            }
            else
            {
                throw new UnconfiguredEntityStorageException();
            }
        }

        /// <summary>
        /// Removes an entity from storage
        /// </summary>
        /// <param name="entity"></param>
        /// <exception cref="UnconfiguredEntityStorageException"></exception>
        public void Remove(IEntity entity)
        {
            Type genericEntityType = entity.GetType();

            Remove(genericEntityType, entity.Key);
        }

        /// <summary>
        /// Removes an entity from storage
        /// </summary>
        /// <param name="type"></param>
        /// <param name="key"></param>
        /// <exception cref="UnconfiguredEntityStorageException"></exception>
        public void Remove(Type type, string key)
        {
            if (StorageMedium.ContainsKey(type))
            {
                Term t = new Term("key", key);

                IndexSet.Get(type).Delete(new TermQuery(t));
                StorageMedium[type].Delete(key);
            }
            else
            {
                throw new UnconfiguredEntityStorageException();
            }
        }

        /// <summary>
        /// Retrieve the entity without using the index
        /// </summary>
        /// <param name="key"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public T Retrieve<T>(string key)
        {
            return StorageMedium[typeof(T)].GetEntity(key) is T ? (T)StorageMedium[typeof(T)].GetEntity(key) : default(T);
        }

        /// <summary>
        /// Get all entities of this type
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public IEnumerable<string> GetAll(Type type)
        {
            return StorageMedium[type].All();
        }

        /// <summary>
        /// Checks if an Entity Type is handled and configured
        /// </summary>
        /// <typeparam name="T">The type of entity</typeparam>
        /// <returns>True if it's handled</returns>
        public bool Exists<T>()
        {
            return StorageMedium.ContainsKey(typeof(T)) && IndexSet.Exists<T>();
        }

        /// <summary>
        /// Simple function to search the entities, by using the index
        /// </summary>
        /// <typeparam name="T">Type of entity</typeparam>
        /// <returns>IIndex of T to search the entities</returns>
        public IIndex<T> Search<T>()
        {
            return IndexSet.Get<T>();
        }

        /// <summary>
        /// Adds a new storage medium to the manager, maps it to an entity type and adds it to the index set
        /// </summary>
        /// <param name="storage">Storage implementation</param>
        /// <typeparam name="T">Type of entity</typeparam>
        public void Add<T>(IEntityStorage storage)
            where T : IEntity
        {
            StorageMedium.Add(typeof(T), storage);
            IndexSet.Add<T>();
        }

        /// <summary>
        /// Removes a storage type from the manager, unmaps it to the type of entity and no longer maintains indexes for this type (index file is preserved)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public void Remove<T>()
            where T : IEntity
        {
            IndexSet.Remove<T>();
            StorageMedium.Remove(typeof(T));
        }

        #region Private Methods
        private IIndex GetIndexSet(IEntity entity)
        {
            return IndexSet.Get(entity.GetType());
        }
        #endregion

        public virtual void Dispose()
        {
            IndexSet.Dispose();
            foreach (IEntityStorage item in StorageMedium.Values)
            {
                item.Dispose();
            }
        }
    }
}